{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit UIGrids;

interface

uses Classes, AHMTypes, CaptionedObject, UIObjects, GridItems, Fills, XMLHelper;

const
  DEFAULT_ROW_HEIGHT = 100;
  DEFAULT_COL_WIDTH = 100;
  DEFAULT_ROW_MARGIN = 0;
  DEFAULT_COL_MARGIN = 0;
  DEFAULT_IMAGE_XPOS = 10;
  DEFAULT_IMAGE_YPOS = 10;
  DEFAULT_IMAGE_WIDTH = 80;
  DEFAULT_IMAGE_HEIGHT = 80;

{$IFDEF DESIGNER}
  MODIFY_STATES   = 'Edit States';
  MODIFY_IMAGES   = 'Edit Default Images';

  // Property categories and hints for designer
  CAT_DESIGN = 'Design';
  CAT_APPEARANCE = 'Appearance';
  CAT_GRID = 'Grid';
  CAT_ITEMS = 'Grid Items';
  HINT_IMAGES = 'specifies default images to use for each grid item';
  HINT_STATES = 'defines bitmaps for each grid state';
  HINT_TEXTITEM = 'specifies the name of a grid item to use for drawing text content';
  HINT_DESCITEM = 'specifies the name of a grid item to use for drawing description content';
  HINT_DATAITEM = 'specifies the name of a grid item to use for drawing data content';
  HINT_IMAGEX = 'specifies horizontal offset for the image position';
  HINT_IMAGEY = 'specifies vertical offset for the image position';
  HINT_IMAGEWIDTH = 'specifies width for each grid item image';
  HINT_IMAGEHEIGHT = 'specifies height for each grid item image';
  HINT_FILL = 'specifies the name of a fill item to use for each grid item image';
  HINT_WIDTH = 'specifies width for the grid';
  HINT_HEIGHT = 'specifies height for the grid';
  HINT_ROWCOUNT = 'specifies the number of visible rows for the grid';
  HINT_COLCOUNT = 'specifies the number of visible columns for the grid';
  HINT_ROWHEIGHT = 'specifies the height of each grid row';
  HINT_COLWIDTH = 'specifies the width of each grid column';
  HINT_ROWMARGIN = 'specifies the vertical margin for each grid row';
  HINT_COLMARGIN = 'specifies the horizontal margin for each grid column';
{$ENDIF}

type
  TAHMGrid = class(TAHMUICaptionedDisplayObject)
  private
    FDefaultImages: TAHMUIStateObjectList;
    FStates: TAHMUIStateObjectList;
    FTextItem: TAHMGridItem;
    FDescItem: TAHMGridItem;
    FDataItem: TAHMGridItem;
    FRowHeight: Single;
    FColWidth: Single;
    FVirtRowHeight: Integer;
    FVirtColWidth: Integer;
    FRowMargin: Single;
    FColMargin: Single;
    FVirtRowMargin: Integer;
    FVirtColMargin: Integer;
    FImageX: Single;
    FImageY: Single;
    FVirtImageX: Integer;
    FVirtImageY: Integer;
    FImageWidth: Single;
    FImageHeight: Single;
    FVirtImageWidth: Integer;
    FVirtImageHeight: Integer;
    FFill: TAHMAbstractFill;
  protected
    procedure SetVirtualRowHeight(Height: Integer);
    procedure SetVirtualColumnWidth(Width: Integer);
    procedure SetVirtualRowMargin(Margin: Integer);
    procedure SetVirtualColumnMargin(Margin: Integer);
    procedure SetVirtualImageX(X: Integer);
    procedure SetVirtualImageY(Y: Integer);
    procedure SetVirtualImageWidth(Width: Integer);
    procedure SetVirtualImageHeight(Height: Integer);
    procedure SetRowCount(Count: Integer);
    procedure SetColCount(Count: Integer);
    function GetVisibleRows: Integer;
    function GetVisibleCols: Integer;
    function GetRowHeight: Single;
    function GetColumnWidth: Single;
    function GetState(Control: TObject; Row, Column: Integer): TAHMControlState;
    procedure ClearProperties; override;
  public
    constructor Create(const Name: String); override;
    destructor Destroy; override;
    property RowHeight: Single read GetRowHeight;
    property ColumnWidth: Single read GetColumnWidth;
    property RowMargin: Single read FRowMargin;
    property ColumnMargin: Single read FColMargin;
    property ImageX: Single read FImageX;
    property ImageY: Single read FImageY;
    property ImageWidth: Single read FImageWidth;
    property ImageHeight: Single read FImageHeight;
    property VisibleRows: Integer read GetVisibleRows;
    property VisibleColumns: Integer read GetVisibleCols;
    function ModifyActions: Integer; override;
    function Modify(Action: Integer): Boolean; override;
    function ModifyCaption(Action: Integer): String; override;
    procedure ConfigurePreview(Control: TObject); override;
    procedure Render(Control: TObject; iX, iY, iWidth, iHeight: Single); override;
    procedure SeedObject; override;
    procedure Assign(Source: TPersistent); override;
    procedure SaveXMLBody(Lines: TStrings); override;
    procedure ParseCommonXML(XML: TAHMXMLHelper); override;
    procedure ParseFromXML(XML: TAHMXMLHelper); override;
    procedure InvalidateUIObject(UIObject: TAHMUIObject); override;
    procedure BuildProperties(Properties: TAHMUIPropertyList); override;
  published
    property RowCount: Integer read GetVisibleRows write SetRowCount;
    property ColumnCount: Integer read GetVisibleCols write SetColCount;
    property VirtualRowHeight: Integer read FVirtRowHeight write SetVirtualRowHeight;
    property VirtualColumnWidth: Integer read FVirtColWidth write SetVirtualColumnWidth;
    property VirtualRowMargin: Integer read FVirtRowMargin write SetVirtualRowMargin;
    property VirtualColumnMargin: Integer read FVirtColMargin write SetVirtualColumnMargin;
    property States: TAHMUIStateObjectList read FStates;
    property DefaultImages: TAHMUIStateObjectList read FDefaultImages;
    property TextItem: TAHMGridItem read FTextItem write FTextItem;
    property DescriptionItem: TAHMGridItem read FDescItem write FDescItem;
    property DataItem: TAHMGridItem read FDataItem write FDataItem;
    property VirtualImageWidth: Integer read FVirtImageWidth write SetVirtualImageWidth;
    property VirtualImageHeight: Integer read FVirtImageHeight write SetVirtualImageHeight;
    property VirtualImageX: Integer read FVirtImageX write SetVirtualImageX;
    property VirtualImageY: Integer read FVirtImageY write SetVirtualImageY;
    property Fill: TAHMAbstractFill read FFill write FFill;
    // Publish inherited properties
    property VirtualWidth;
    property VirtualHeight;
  end;

  TAHMGridList = class(TAHMUIObjectList)
  private
  protected
  public
    constructor Create; override;
    function AddGrid(const InheritFrom, NewName: String): TAHMGrid;
    function GetGrid(const Name: String): TAHMGrid;
    procedure PopulateDefaultObjects; override;
  end;

implementation

uses SysUtils, ApplicationManager, Canvas, BaseControls, UIControls,
     GridObjects, Bitmaps, {$IFDEF DESIGNER}DesignerStateList,{$ENDIF}
     Screen, MetaConsts, TagNames, XMLTags, ErrorCodes;


constructor TAHMGridList.Create;
begin
  inherited Create;

  XMLTagName := TAG_GRIDS;
  ChildClass := TAHMGrid;
end;

function TAHMGridList.AddGrid(const InheritFrom, NewName: String): TAHMGrid;
begin
  Result := TAHMGrid(Inherit(InheritFrom, NewName));
end;

function TAHMGridList.GetGrid(const Name: String): TAHMGrid;
begin
  Result := TAHMGrid(GetObject(Name));
end;

procedure TAHMGridList.PopulateDefaultObjects;
begin
  with ApplicationManagerInstance.ThemeManager, DefaultObject as TAHMGrid do
  begin
    VirtualWidth := DEFAULT_CTRL_WIDTH * 2;
    VirtualHeight := DEFAULT_CTRL_WIDTH;
    VirtualImageWidth := DEFAULT_CTRL_HEIGHT div 2;
    VirtualImageHeight := DEFAULT_CTRL_HEIGHT div 2;
    RowCount := 5;
    ColumnCount := 1;
    TextItem := TAHMGridItem(ThemeObjects[otGridItems].DefaultObject);
  end;
end;

//----------------------------------------------------------------------------//

constructor TAHMGrid.Create(const Name: String);
begin
  inherited Create(Name);

  XMLTagName := TAG_GRID;
  ObjectType := otGrids;
  ImageIndex := Ord(otGrids);

  FStates := TAHMUIStateObjectList.Create;
  FStates.ObjectTypes := [otBitmaps];
  FStates.Owner := Self;
  FDefaultImages := TAHMUIStateObjectList.Create;
  FDefaultImages.ObjectTypes := [otBitmaps];
  FDefaultImages.StateType := stCustom;
  FDefaultImages.Owner := Self;

{$IFDEF DESIGNER}
  FStates.DisplayClassName := 'Grid state';
  FDefaultImages.DisplayClassName := 'Grid default image';

  // Render preview using a grid control
  PreviewControlClass := TAHMGridControl;
{$ENDIF}

  // Set default property values
  ClearProperties;
end;

destructor TAHMGrid.Destroy;
begin
  FStates.Free;
  FDefaultImages.Free;

  inherited Destroy;
end;

procedure TAHMGrid.SetVirtualRowHeight(Height: Integer);
begin
  if (Height <> FVirtRowHeight) and (Height > 0) then
  begin
    FVirtRowHeight := Height;
    FRowHeight := ScreenInstance.ScaleVirtualY(Height);
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  end;
end;

procedure TAHMGrid.SetVirtualColumnWidth(Width: Integer);
begin
  if (Width <> FVirtColWidth) and (Width > 0) then
  begin
    FVirtColWidth := Width;
    FColWidth := ScreenInstance.ScaleVirtualX(Width);
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  end;
end;

procedure TAHMGrid.SetVirtualRowMargin(Margin: Integer);
begin
  if Margin <> FVirtRowMargin then
  begin
    FVirtRowMargin := Margin;
    FRowMargin := ScreenInstance.ScaleVirtualY(Margin);
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  end;
end;

procedure TAHMGrid.SetVirtualColumnMargin(Margin: Integer);
begin
  if Margin <> FVirtColMargin then
  begin
    FVirtColMargin := Margin;
    FColMargin := ScreenInstance.ScaleVirtualX(Margin);
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  end;
end;

procedure TAHMGrid.SetVirtualImageX(X: Integer);
begin
  FVirtImageX := X;
  FImageX := ScreenInstance.ScaleVirtualX(X);
end;

procedure TAHMGrid.SetVirtualImageY(Y: Integer);
begin
  FVirtImageY := Y;
  FImageY := ScreenInstance.ScaleVirtualY(Y);
end;

procedure TAHMGrid.SetVirtualImageWidth(Width: Integer);
begin
  if Width <> FVirtImageWidth then
  begin
    FVirtImageWidth := Width;
    FImageWidth := ScreenInstance.ScaleVirtualX(Width);
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  end;
end;

procedure TAHMGrid.SetVirtualImageHeight(Height: Integer);
begin
  if Height <> FVirtImageHeight then
  begin
    FVirtImageHeight := Height;
    FImageHeight := ScreenInstance.ScaleVirtualY(Height);
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  end;
end;

procedure TAHMGrid.SetRowCount(Count: Integer);
begin
  // Adjust row height to fit requested number of rows
  if Count > 0 then
    SetVirtualRowHeight((VirtualHeight - (FVirtRowMargin * Count * 2)) div Count);
end;

procedure TAHMGrid.SetColCount(Count: Integer);
begin
  // Adjust column width to fit requested number of columns
  if Count > 0 then
    SetVirtualColumnWidth((VirtualWidth - (FVirtColMargin * Count * 2)) div Count);
end;

function TAHMGrid.GetVisibleRows: Integer;
begin
  if RowHeight > 0 then Result := Round(Height / RowHeight)
                   else Result := 0;
end;

function TAHMGrid.GetVisibleCols: Integer;
begin
  if ColumnWidth > 0 then Result := Round(Width / ColumnWidth)
                     else Result := 0;
end;

function TAHMGrid.GetRowHeight: Single;
begin
  // Adjust row height to include vertical margins
  Result := FRowHeight + FRowMargin + FRowMargin;
end;

function TAHMGrid.GetColumnWidth: Single;
begin
  // Adjust column width to include horizontal margins
  Result := FColWidth + FColMargin + FColMargin;
end;

function TAHMGrid.GetState(Control: TObject; Row, Column: Integer): TAHMControlState;
begin
  // Cell state depends on control state as well as selection position
  with Control as TAHMGridControl do
  begin
    Result := ControlState;

    // There can only be one focussed or pressed cell
    case Result of
      csIdle: if (Row = SelectedRow) and (Column = SelectedCol) then Result := csDisabled;
      csFocused, csPressed: if (Row <> SelectedRow) or (Column <> SelectedCol) then Result := csIdle;
    end;
  end;
end;

procedure TAHMGrid.ClearProperties;
begin
  inherited;

  // Clear custom grid properties
  FStates.Clear;
  FDefaultImages.Clear;
  FTextItem := nil;
  FDescItem := nil;
  FDataItem := nil;
  FFill := nil;
  VirtualRowHeight := DEFAULT_ROW_HEIGHT;
  VirtualColumnWidth := DEFAULT_COL_WIDTH;
  VirtualRowMargin := DEFAULT_ROW_MARGIN;
  VirtualColumnMargin := DEFAULT_COL_MARGIN;
  VirtualImageX := DEFAULT_IMAGE_XPOS;
  VirtualImageY := DEFAULT_IMAGE_YPOS;
  VirtualImageWidth := DEFAULT_IMAGE_WIDTH;
  VirtualImageHeight := DEFAULT_IMAGE_HEIGHT;
end;

function TAHMGrid.ModifyActions: Integer;
begin
  // Grid supports editing states and default images
  Result := 2;
end;

function TAHMGrid.Modify(Action: Integer): Boolean;
begin
{$IFDEF DESIGNER}
  case Action of
    0: // Edit states with list dialog
       Result := TfrmObjectStateList.ShowList(FStates);
    1: // Edit default images with list dialog
       Result := TfrmObjectStateList.ShowList(FDefaultImages);
    else Result := False;
  end;

  if Result then DoModified;
{$ELSE}
  Result := False;
{$ENDIF}
end;

function TAHMGrid.ModifyCaption(Action: Integer): String;
begin
{$IFDEF DESIGNER}
  case Action of
    0: Result := MODIFY_STATES;
    1: Result := MODIFY_IMAGES;
    else Result := '';
  end;
{$ELSE}
  Result := '';
{$ENDIF}
end;

procedure TAHMGrid.ConfigurePreview(Control: TObject);
begin
{$IFDEF DESIGNER}
  with Control as TAHMGridControl do
  begin
    // Setup metadata for grid
    MetaGroup := META_PREVIEW_GRID_VALUE;
    MetaIndex := META_PREVIEW_GRID_INDEX;
  end;
{$ENDIF}
end;

procedure TAHMGrid.Render(Control: TObject; iX, iY, iWidth, iHeight: Single);
var
  oStateObject: TAHMUIDisplayObject;
  iXPos, iYPos, iImgX, iImgY: Single;
  iLeft, iTop, iRow, iCol, iRows, iCols: Integer;
  DispRowHeight, DispColWidth: Single;
  GridObj: TAHMGridObject;
  OldFill: TAHMAbstractFill;
  EffectRect: TAHMRectF;
  DrawFocus, DrawPress: Boolean;
begin
  // Pre-render our control if necessary
  inherited;

  // Calculate visible dimensions of each cell
  DispColWidth := GetColumnWidth;
  DispRowHeight := GetRowHeight;

  with Control as TAHMGridControl do
  begin
    // Cache visible rows and visible columns calculation
    iRows := Pred(VisibleRows);
    iCols := Pred(VisibleColumns);
    iTop := 0; iLeft := 0;

    // Cache offset calculations for grid images - allow for flipping
    if HorizontalFlip then iImgX := FColWidth - FImageX - FImageWidth else iImgX := FImageX;
    if VerticalFlip then iImgY := FRowHeight - FImageY - FImageHeight else iImgY := FImageY;

    // Render an extra row/column if using partial offsets
    if RowOffset > 0 then Dec(iTop) else if RowOffset < 0 then Inc(iRows);
    if ColOffset > 0 then Dec(iLeft) else if ColOffset < 0 then Inc(iCols);

    for iRow := iTop to iRows do
      for iCol := iLeft to iCols do
      begin
        // Determine which object to render here
        GridObj := GridObject[iRow + TopRow, iCol + LeftCol];
        if not Assigned(GridObj) then Continue;

        // Update object state and determine state specific rendering
        with GridObj do
        begin
          State := GetState(Control, TopRow + iRow, LeftCol + iCol);
          oStateObject := TAHMUIDisplayObject(FStates.GetStateObject(State, Checked));
        end;

        // We only want scrolling to happen for the focussed grid object
        UpdateScroller((GridObj.State in [csFocused, csPressed, csDisabled]));

        // Calculate coordinates for this grid cell - allow for flipping
        if HorizontalFlip then
          iXPos := iX + iWidth - (FColMargin + (DispColWidth * (iCol + 1)) + ColOffset)
        else
          iXPos := iX + FColMargin + (DispColWidth * iCol) + ColOffset;
        if VerticalFlip then
          iYPos := iY + iHeight - (FRowMargin + (DispRowHeight * (iRow + 1)) + RowOffset)
        else
          iYPos := iY + FRowMargin + (DispRowHeight * iRow) + RowOffset;

        with Viewport do
        begin
          // Render state object into our cell coordinates
          if Assigned(oStateObject) then
            oStateObject.Render(Control, iXPos, iYPos, FColWidth, FRowHeight, True);

          with GridObj do
          begin
            // Provide a shared bitmap if image not yet loaded
            if (ImageIndex <> '') and (Bitmap = nil) and not SharedBitmap then
              ShareBitmap(TAHMBitmap(FDefaultImages.GetState(ImageIndex)));

            // If we should use a custom image then try to load it
            if (ImageURL <> '') and (SharedBitmap or (Bitmap = nil)) then
              LoadBitmapThumbnail(FImageWidth, FImageHeight);

            // Initialise rendering effects for this cell
            DrawFocus := GridObj.State in [csFocused, csPressed];
            DrawPress := GridObj.State in [csPressed];
            InitialiseCustomEffects(DrawFocus, DrawPress, Checked);

            // Render grid item image over background. Custom thumbnails are
            // sized/centred to preserve image aspect ratio
            if Assigned(Bitmap) then
            begin
              OldFill := Bitmap.Fill;
              if Assigned(FFill) then Bitmap.Fill := FFill;
              if SharedBitmap then
                Bitmap.Render(Control, iXPos + iImgX, iYPos + iImgY, FImageWidth, FImageHeight, True)
              else
                Bitmap.Render(Control, iXPos + iImgX + ((FImageWidth - Bitmap.Width) / 2),
                              iYPos + iImgY + ((FImageHeight - Bitmap.Height) / 2),
                              Bitmap.Width, Bitmap.Height, True);
              Bitmap.Fill := OldFill;
            end;

            // Render grid items
            if Assigned(FTextItem) then
              FTextItem.Render(Control, GridObj, Text, iXPos, iYPos, FColWidth, FRowHeight, True);
            if Assigned(FDescItem) then
              FDescItem.Render(Control, GridObj, Description, iXPos, iYPos, FColWidth, FRowHeight, True);
            if Assigned(FDataItem) then
              FDataItem.Render(Control, GridObj, Data, iXPos, iYPos, FColWidth, FRowHeight, True);

            // Render appropriate state effects for this grid item
            EffectRect := MakeRectF(iXPos, iYPos, FColWidth, FRowHeight);
            RenderCustomEffects(EffectRect, DrawFocus, DrawPress, Checked);
          end;
        end;
      end;
  end;
end;

procedure TAHMGrid.SeedObject;
begin
{$IFDEF DESIGNER}
  if Ancestor = nil then
    with ApplicationManagerInstance.ThemeManager.VisualDescriptor do
    begin
      VirtualWidth := DEFAULT_CTRL_WIDTH * 2;
      VirtualHeight := DEFAULT_CTRL_WIDTH;
      VirtualImageWidth := DEFAULT_CTRL_HEIGHT div 2;
      VirtualImageHeight := DEFAULT_CTRL_HEIGHT div 2;
      RowCount := 5;
      ColumnCount := 1;
      TextItem := GridItemList.GetGridItem(DEFAULT_OBJECT_NAME);
    end;
{$ENDIF}
end;

procedure TAHMGrid.Assign(Source: TPersistent);
begin
  inherited Assign(Source);

  with Source as TAHMGrid do
  begin
    Self.VirtualRowHeight := VirtualRowHeight;
    Self.VirtualColumnWidth := VirtualColumnWidth;
    Self.VirtualRowMargin := VirtualRowMargin;
    Self.VirtualColumnMargin := VirtualColumnMargin;
    Self.VirtualImageX := VirtualImageX;
    Self.VirtualImageY := VirtualImageY;
    Self.VirtualImageWidth := VirtualImageWidth;
    Self.VirtualImageHeight := VirtualImageHeight;
    Self.States.Assign(States);
    Self.DefaultImages.Assign(DefaultImages);
    Self.TextItem := TextItem;
    Self.DescriptionItem := DescriptionItem;
    Self.DataItem := DataItem;
    Self.Fill := Fill;
  end;
end;

procedure TAHMGrid.SaveXMLBody(Lines: TStrings);
var
  oAncs: TAHMGrid;
  oObj, oFont, oCaption: TAHMUIObject;
  sWork, sName, sBitmap: String;
  i: Integer;
begin
  // Determine which ancestor to compare against - real or empty
  oAncs := TAHMGrid(ObjectToCompare);

  // Do we need to store grid item properties?
  if ((FTextItem <> nil) and (FTextItem <> oAncs.TextItem)) or
     ((FDescItem <> nil) and (FDescItem <> oAncs.DescriptionItem)) or
     ((FDataItem <> nil) and (FDataItem <> oAncs.DataItem)) then
  begin
    Lines.Add(TABS[3] + TGB+TAG_GRID_ITEMS+TGC);
    if ((FTextItem <> nil) and (FTextItem <> oAncs.TextItem)) then
      Lines.Add(TABS[4] + TGB+TAG_GRID_ITEM+SPC+ATTR_TYPE+ATB+ATTR_VAL_TEXT +
                ATN+ATTR_NAME+ATB + FTextItem.Name + ATE);
    if ((FDescItem <> nil) and (FDescItem <> oAncs.DescriptionItem)) then
      Lines.Add(TABS[4] + TGB+TAG_GRID_ITEM+SPC+ATTR_TYPE+ATB+ATTR_VAL_DESCR +
                ATN+ATTR_NAME+ATB + FDescItem.Name + ATE);
    if ((FDataItem <> nil) and (FDataItem <> oAncs.DataItem)) then
      Lines.Add(TABS[4] + TGB+TAG_GRID_ITEM+SPC+ATTR_TYPE+ATB+ATTR_VAL_DATA +
                ATN+ATTR_NAME+ATB + FDataItem.Name + ATE);
    Lines.Add(TABS[3] + TGE+TAG_GRID_ITEMS+TGC);
  end;

  // Do we need to store image properties?
  if ((FVirtImageX <> DEFAULT_IMAGE_XPOS) and (FVirtImageX <> oAncs.VirtualImageX)) or
     ((FVirtImageY <> DEFAULT_IMAGE_YPOS) and (FVirtImageY <> oAncs.VirtualImageY)) or
     ((FVirtImageWidth <> DEFAULT_IMAGE_WIDTH) and (FVirtImageWidth <> oAncs.VirtualImageWidth)) or
     ((FVirtImageHeight <> DEFAULT_IMAGE_HEIGHT) and (FVirtImageHeight <> oAncs.VirtualImageHeight)) or
     ((FFill <> nil) and (FFill <> oAncs.Fill)) then
  begin
    Lines.Add(TABS[3] + TGB+TAG_IMAGE+TGC);
    // Do we need to store size?
    if ((FVirtImageWidth <> DEFAULT_IMAGE_WIDTH) and (FVirtImageWidth <> oAncs.VirtualImageWidth)) or
       ((FVirtImageHeight <> DEFAULT_IMAGE_HEIGHT) and (FVirtImageHeight <> oAncs.VirtualImageHeight)) then
      Lines.Add(TABS[4] + TGB+TAG_SIZE+TGC+ IntToStr(FVirtImageWidth) + ',' +
                IntToStr(FVirtImageHeight) + TGE+TAG_SIZE+TGC);
    // Do we need to store position?
    if ((FVirtImageX <> DEFAULT_IMAGE_XPOS) and (FVirtImageX <> oAncs.VirtualImageX)) or
       ((FVirtImageY <> DEFAULT_IMAGE_YPOS) and (FVirtImageY <> oAncs.VirtualImageY)) then
      Lines.Add(TABS[4] + TGB+TAG_POSITION+TGC+ IntToStr(FVirtImageX) + ',' +
                IntToStr(FVirtImageY) + TGE+TAG_POSITION+TGC);
    // Do we need to store fill?
    if (FFill <> nil) and (FFill <> oAncs.Fill) then
      Lines.Add(TABS[4] + FFill.XMLReference);
    Lines.Add(TABS[3] + TGE+TAG_IMAGE+TGC);
  end;

  // Do we need to store grid properties?
  if ((FVirtRowHeight <> DEFAULT_ROW_HEIGHT) and (FVirtRowHeight <> oAncs.VirtualRowHeight)) or
     ((FVirtColWidth <> DEFAULT_COL_WIDTH) and (FVirtColWidth <> oAncs.VirtualColumnWidth)) or
     ((FVirtRowMargin <> DEFAULT_ROW_MARGIN) and (FVirtRowMargin <> oAncs.VirtualRowMargin)) or
     ((FVirtColMargin <> DEFAULT_COL_MARGIN) and (FVirtColMargin <> oAncs.VirtualColumnMargin)) then
  begin
    Lines.Add(TABS[3] + sWork + TGB+TAG_PROPERTIES+TGC);
    // Do we need to store row height or column width?
    if (FVirtRowHeight <> DEFAULT_ROW_HEIGHT) and (FVirtRowHeight <> oAncs.VirtualRowHeight) then
      Lines.Add(TABS[4] + TGB+TAG_ROWS+SPC+ATTR_HEIGHT+ATB + IntToStr(FVirtRowHeight) + ATE);
    if (FVirtColWidth <> DEFAULT_COL_WIDTH) and (FVirtColWidth <> oAncs.VirtualColumnWidth) then
      Lines.Add(TABS[4] + TGB+TAG_COLUMNS+SPC+ATTR_WIDTH+ATB + IntToStr(FVirtColWidth) + ATE);
    // Do we need to store margins?
    if ((FVirtRowMargin <> DEFAULT_ROW_MARGIN) and (FVirtRowMargin <> oAncs.VirtualRowMargin)) or
       ((FVirtColMargin <> DEFAULT_COL_MARGIN) and (FVirtColMargin <> oAncs.VirtualColumnMargin)) then
    begin
      sWork := TGB+TAG_MARGINS;
      if (FVirtRowMargin <> DEFAULT_ROW_MARGIN) and (FVirtRowMargin <> oAncs.VirtualRowMargin) then
        sWork := sWork + SPC+ATTR_HEIGHT+ATB + IntToStr(FVirtRowMargin) + ATQ;
      if (FVirtColMargin <> DEFAULT_COL_MARGIN) and (FVirtColMargin <> oAncs.VirtualColumnMargin) then
        sWork := sWork + SPC+ATTR_WIDTH+ATB + IntToStr(FVirtColMargin) + ATQ;
      Lines.Add(TABS[4] + sWork + ETE);
    end;
    Lines.Add(TABS[3] + TGE+TAG_PROPERTIES+TGC);
  end;

  // Do we need to store states?
  if FStates.IsDifferent(oAncs.States) then
  begin
    Lines.Add(TABS[3] + TGB+TAG_STATES+TGC);
    for i := 0 to High(ControlStateNames) do
    begin
      oObj := FStates.GetState(ControlStateNames[i]);

      if Assigned(oObj) then
      begin
        Lines.Add(TABS[4] + TGB+TAG_STATE+SPC+ATTR_NAME+ATB + EncodeXML(ControlStateNames[i]) + ATC);
        Lines.Add(TABS[5] + oObj.XMLReference);
        Lines.Add(TABS[4] + TGE+TAG_STATE+TGC);
      end;
    end;
    Lines.Add(TABS[3] + TGE+TAG_STATES+TGC);
  end;

  // Do we need to store size?
  if ((VirtualWidth <> 0) and (VirtualWidth <> oAncs.VirtualWidth)) or
     ((VirtualHeight <> 0) and (VirtualHeight <> oAncs.VirtualHeight)) then
    Lines.Add(TABS[3] + TGB+TAG_SIZE+TGC+ IntToStr(VirtualWidth) + ',' +
              IntToStr(VirtualHeight) + TGE+TAG_SIZE+TGC);

  // Do we need to store default images?
  if FDefaultImages.IsDifferent(oAncs.DefaultImages) then
  begin
    Lines.Add(TABS[3] + TGB+TAG_DEFAULTIMAGES+TGC);
    for i := 0 to Pred(FDefaultImages.Count) do
    begin
      sName := FDefaultImages.States[i];
      sBitmap := FDefaultImages.Objects[i].Name;
      if (sName <> '') and (sBitmap <> '') then
      begin
        Lines.Add(TABS[4] + TGB+TAG_IMAGE+SPC+ATTR_NAME+ATB + EncodeXML(sName) + ATC);
        Lines.Add(TABS[5] + TGB+TAG_BITMAP+SPC+ATTR_NAME+ATB + EncodeXML(sBitmap) + ATE);
        Lines.Add(TABS[4] + TGE+TAG_IMAGE+TGC);
      end;
    end;
    Lines.Add(TABS[3] + TGE+TAG_DEFAULTIMAGES+TGC);
  end;
end;

procedure TAHMGrid.ParseCommonXML(XML: TAHMXMLHelper);
var
  sName, sCaption, sState, sDest, sWidth, sHeight, sFill: String;
  iX, iY: Integer;
  PropsNode, DetailNode, StateNode: TAHMXMLNode;

  // Local function to handle creation of default text item for deprecated tags
  function DefaultTextItem: TAHMGridItem;
  begin
    if not Assigned(FTextItem) then
      with ApplicationManagerInstance.ThemeManager.VisualDescriptor do
        FTextItem := GridItemList.AddGridItem('', Self.Name + 'Text');
    Result := FTextItem;
  end;

begin
  // Initialise parsing nodes
  PropsNode := XML.CurrentNode;
  DetailNode := nil;

  with ApplicationManagerInstance.ThemeManager.VisualDescriptor do
    if XML.CurrentElement = TAG_GRID_ITEMS then
    begin
      // Handle grid items
      while XML.FindElement(PropsNode, DetailNode, TAG_GRID_ITEM) do
      begin
        sState := XML.GetAttribute(ATTR_TYPE);
        sName := XML.GetAttribute(ATTR_NAME);
        if sState = ATTR_VAL_TEXT then
          FTextItem := GridItemList.GetGridItem(sName)
        else if sState = ATTR_VAL_DESCR then
          FDescItem := GridItemList.GetGridItem(sName)
        else if sState = ATTR_VAL_DATA then
          FDataItem := GridItemList.GetGridItem(sName);
      end;
    end
    else if XML.CurrentElement = TAG_CAPTION then
    begin
      // Handle grid captions (deprecated)
      sCaption := XML.GetAttribute(ATTR_NAME);
      if sCaption <> '' then DefaultTextItem.Caption := CaptionList.GetCaption(sCaption);

      while XML.NextElement(PropsNode, DetailNode) do
      begin
        // Check for caption position
        if XML.CurrentElement = TAG_POSITION then
        begin
          if XML.GetCoordsValue(iX, iY) then
          begin
            DefaultTextItem.VirtualCaptionX := iX;
            DefaultTextItem.VirtualCaptionY := iY;
          end
          else
            LogParsingError(THM_PRS_VIS_MALFORMED_GD_POSITION_TAGS, Self);
        end;
      end;
    end
    else if XML.CurrentElement = TAG_STATES then
    begin
      // Clear current states before parsing
      States.Clear;

      // Handle grid states
      while XML.FindElement(PropsNode, DetailNode, TAG_STATE) do
      begin
        sState := XML.GetAttribute(ATTR_NAME);
        StateNode := nil;

        // We may have multiple state properties
        while XML.NextElement(DetailNode, StateNode) do
        begin
          // What type of object do we want to use for this state?
          // Note that font and caption objects are now deprecated as
          // they are stored within the TAHMGridItem class instead.
          // Shapes are also deprecated and silently ignored
          sDest := XML.GetAttribute(ATTR_NAME);
          if XML.CurrentElement = TAG_BITMAP then
            States.AddObject(sState, BitmapList.GetBitmap(sDest))
          else if XML.CurrentElement = TAG_FONT then
            DefaultTextItem.StateFonts.AddObject(sState, FontList.GetFont(sDest))
          else if XML.CurrentElement = TAG_CAPTION then
            DefaultTextItem.StateCaptions.AddObject(sState, CaptionList.GetCaption(sDest))
          else if XML.CurrentElement <> TAG_SHAPE then
            LogParsingError(THM_PRS_VIS_UNSUPPORTED_GRID_STATE_TYPE, Self);
        end;
      end;
    end
    else if XML.CurrentElement = TAG_IMAGE then
    begin
      // Handle grid image properties
      while XML.NextElement(PropsNode, DetailNode) do
      begin
        if XML.CurrentElement = TAG_SIZE then
        begin
          if XML.GetCoordsValue(iX, iY) then
          begin
            VirtualImageWidth := iX;
            VirtualImageHeight := iY;
          end
          else
            LogParsingError(THM_PRS_VIS_MALFORMED_GRID_SIZE_TAGS, Self);
        end
        else if XML.CurrentElement = TAG_POSITION then
        begin
          if XML.GetCoordsValue(iX, iY) then
          begin
            VirtualImageX := iX;
            VirtualImageY := iY;
          end
          else
            LogParsingError(THM_PRS_VIS_MALFORMED_GRID_POS_TAGS, Self);
        end
        else if (XML.CurrentElement = TAG_FILL) or
                (XML.CurrentElement = TAG_GRADEDFILL) or
                (XML.CurrentElement = TAG_LINEARFILL) then
        begin
          sFill := XML.GetAttribute(ATTR_NAME);
          if sFill <> '' then Fill := FillList.GetFill(sFill);
        end
        else
          LogParsingError(THM_PRS_VIS_UNSUPPORTED_GRID_IMAGE_PROP, Self);
      end;
    end
    else if XML.CurrentElement = TAG_PROPERTIES then
    begin
      // Handle additional grid properties
      while XML.NextElement(PropsNode, DetailNode) do
      begin
        if XML.CurrentElement = TAG_COLUMNS then
        begin
          sWidth := XML.GetAttribute(ATTR_WIDTH);
          if sWidth <> '' then VirtualColumnWidth := StrToInt(sWidth);
        end
        else if XML.CurrentElement = TAG_ROWS then
        begin
          sHeight := XML.GetAttribute(ATTR_HEIGHT);
          if sHeight <> '' then VirtualRowHeight := StrToInt(sHeight);
        end
        else if XML.CurrentElement = TAG_MARGINS then
        begin
          sWidth := XML.GetAttribute(ATTR_WIDTH);
          sHeight := XML.GetAttribute(ATTR_HEIGHT);
          if sWidth <> '' then VirtualColumnMargin := StrToInt(sWidth);
          if sHeight <> '' then VirtualRowMargin := StrToInt(sHeight);
        end;
      end;
    end
    else if XML.CurrentElement = TAG_SIZE then
    begin
      if XML.GetCoordsValue(iX, iY) then
      begin
        VirtualWidth := iX;
        VirtualHeight := iY;
      end
      else
        LogParsingError(THM_PRS_VIS_MALFORMED_GRID_SIZE_TAGS, Self);
    end
    else if XML.CurrentElement = TAG_DEFAULTIMAGES then
    begin
      // Handle grid default images
      DetailNode := nil;
      while XML.FindElement(PropsNode, DetailNode, TAG_IMAGE) do
      begin
        // We only expect to get bitmap elements here
        sName := XML.GetAttribute(ATTR_NAME);
        if not XML.ReadElementAttribute(TAG_BITMAP, ATTR_NAME, sDest) then
          LogParsingError(THM_PRS_VIS_MALFORMED_GRID_IMAGE_TAGS, Self);
        FDefaultImages.AddObject(sName, BitmapList.GetBitmap(sDest));
      end;
    end;
end;

procedure TAHMGrid.ParseFromXML(XML: TAHMXMLHelper);
var
  GridNode, PropsNode: TAHMXMLNode;
begin
  // Initialise parsing nodes
  GridNode := XML.CurrentNode;
  PropsNode := nil;

  // Use above method to process common grid elements
  while XML.NextElement(GridNode, PropsNode) do
    ParseCommonXML(XML);
end;

procedure TAHMGrid.InvalidateUIObject(UIObject: TAHMUIObject);
begin
  // Changes to shapes or bitmaps will invalidate the grid
  if FStates.Contains(UIObject) then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
end;

procedure TAHMGrid.BuildProperties(Properties: TAHMUIPropertyList);
begin
{$IFDEF DESIGNER}
  inherited;

  // Build custom grid properties
  Properties.Add('States', 'States', CAT_APPEARANCE, HINT_STATES);
  Properties.Add('Default Images', 'DefaultImages', CAT_APPEARANCE, HINT_IMAGES);
  Properties.Add('Text Item', 'TextItem', CAT_ITEMS, HINT_TEXTITEM);
  Properties.Add('Description Item', 'DescriptionItem', CAT_ITEMS, HINT_DESCITEM);
  Properties.Add('Data Item', 'DataItem', CAT_ITEMS, HINT_DATAITEM);
  Properties.Add('Image X', 'VirtualImageX', CAT_DESIGN, HINT_IMAGEX);
  Properties.Add('Image Y', 'VirtualImageY', CAT_DESIGN, HINT_IMAGEY);
  Properties.Add('Image Width', 'VirtualImageWidth', CAT_DESIGN, HINT_IMAGEWIDTH);
  Properties.Add('Image Height', 'VirtualImageHeight', CAT_DESIGN, HINT_IMAGEHEIGHT);
  Properties.Add('Image Fill', 'Fill', CAT_DESIGN, HINT_FILL);
  Properties.Add('Width', 'VirtualWidth', CAT_DESIGN, HINT_WIDTH);
  Properties.Add('Height', 'VirtualHeight', CAT_DESIGN, HINT_HEIGHT);
  Properties.Add('Rows', 'RowCount', CAT_GRID, HINT_ROWCOUNT);
  Properties.Add('Columns', 'ColumnCount', CAT_GRID, HINT_COLCOUNT);
  Properties.Add('Row Height', 'VirtualRowHeight', CAT_GRID, HINT_ROWHEIGHT);
  Properties.Add('Column Width', 'VirtualColumnWidth', CAT_GRID, HINT_COLWIDTH);
  Properties.Add('Row Margin', 'VirtualRowMargin', CAT_GRID, HINT_ROWMARGIN);
  Properties.Add('Column Margin', 'VirtualColumnMargin', CAT_GRID, HINT_COLMARGIN);
{$ENDIF}
end;

end.
